package codec

//
// #cgo LDFLAGS: -L/usr/local/lib -lavformat -lavcodec -lavutil -lswscale
// #cgo CFLAGS: -I/usr/local/include
// #include <libavcodec/avcodec.h>
// #include <libavformat/avformat.h>
// #include <libavutil/avutil.h>
// #include <libavutil/imgutils.h>
// #include <libswscale/swscale.h>
// #include <inttypes.h>
// #include <libavutil/avutil.h>
// #include <libavformat/avformat.h>
//
// int run_decoder(void* decoder);
//
import "C"
import (
	"errors"
	"fmt"
	"go-scrcpy/internal/sink"
	"go-scrcpy/internal/source"
	"go-scrcpy/pkg/event"
	"go-scrcpy/pkg/log"
	"io"
	"reflect"
	"runtime"
	"sync"
	"unsafe"
)

const EventNewFrame = 1
const EventDecoderStopped = 2

type NewFrameEvent struct {
	Type       uint32 // value obtained from RegisterEvents()
	Timestamp  uint32 // timestamp of the event
	DeviceName string
	Data1      unsafe.Pointer
}

// GetType returns the event type.
func (e *NewFrameEvent) GetType() uint32 {
	return e.Type
}

// GetTimestamp returns the timestamp of the event.
func (e *NewFrameEvent) GetTimestamp() uint32 {
	return e.Timestamp
}

type StoppedEvent struct {
	Type       uint32 // value obtained from RegisterEvents()
	Timestamp  uint32 // timestamp of the event
	DeviceName string
}

// GetType returns the event type.
func (e *StoppedEvent) GetType() uint32 {
	return e.Type
}

// GetTimestamp returns the timestamp of the event.
func (e *StoppedEvent) GetTimestamp() uint32 {
	return e.Timestamp
}

var errAVAlloc = errors.New("av_frame_alloc() fail")

type AVFrame uintptr
type AVDecoder uintptr

func (af AVFrame) Width() int {
	return int((*C.AVFrame)(unsafe.Pointer(af)).width)
}

func (af AVFrame) Height() int {
	return int((*C.AVFrame)(unsafe.Pointer(af)).height)
}

func (af AVFrame) Free() {
	tmp := (*C.AVFrame)(unsafe.Pointer(af))
	C.av_frame_free(&tmp)
}

func (af AVFrame) Data(i int) (ret []byte) {
	tmp := (*C.AVFrame)(unsafe.Pointer(af))
	if tmp.data[C.int(i)] == nil {
		return nil
	}
	p := (*reflect.SliceHeader)(unsafe.Pointer(&ret))
	p.Data = uintptr(unsafe.Pointer(tmp.data[C.int(i)]))
	lineSize := af.LineSize(i)
	if i == 1 {
		p.Len = (lineSize * af.Height()) >> 1
	} else {
		p.Len = lineSize * af.Height()
	}
	p.Cap = p.Len
	return
}

func (af AVFrame) LineSize(i int) int {
	tmp := (*C.AVFrame)(unsafe.Pointer(af))
	return int(tmp.linesize[C.int(i)])
}

func (af AVFrame) copy(b []byte) []byte {
	buf1, buf2 := af.Data(0), af.Data(1)
	if buf1 == nil || buf2 == nil {
		return b
	}
	if len(b) < len(buf1)+len(buf2) {
		b = make([]byte, len(buf1)+len(buf2))
	}
	n := copy(b, buf1)
	copy(b[n:], buf2)
	return b
}

func (af AVFrame) isEmpty() bool {
	tmp := (*C.AVFrame)(unsafe.Pointer(af))
	return tmp.data[0] == nil
}

type FrameFactory struct {
	framesChan chan AVFrame
}

func NewFrameFactory() (*FrameFactory, func(), error) {
	framesChan := make(chan AVFrame, 6)
	for i := 0; i < 6; i++ {
		frame := AVFrame(unsafe.Pointer(C.av_frame_alloc()))
		if frame == 0 {
			log.Error("%v", errAVAlloc)
		} else {
			framesChan <- frame
		}
	}
	f := &FrameFactory{
		framesChan: framesChan,
	}

	return f, func() {
		_ = f.Close()
	}, nil
}

func (f *FrameFactory) Close() error {
	close(f.framesChan)
	for frame := range f.framesChan {
		frame.Free()
	}
	return nil
}

func (f *FrameFactory) OfferFrame() AVFrame {
	return <-f.framesChan
}

func (f *FrameFactory) PutFrame(frame AVFrame) {
	f.framesChan <- frame
}

type Decoder struct {
	*FrameFactory
	videoSource source.Source
	videoSink   sink.Sink
	wg          sync.WaitGroup
	swsCtx      *C.struct_SwsContext
	rgbaFrame   *C.AVFrame
	bufSize     C.int
}

func NewDecoder(f *FrameFactory, source source.Source, vsink sink.Sink) *Decoder {
	return &Decoder{FrameFactory: f, videoSource: source, videoSink: vsink}
}

func (d *Decoder) Stop() {
	d.wg.Wait()
	d.FrameFactory.Close()
}

func (d *Decoder) Start() error {
	d.rgbaFrame = C.av_frame_alloc()

	if d.rgbaFrame == nil {
		return fmt.Errorf(
			"couldn't allocate a new RGBA frame")
	}

	d.bufSize = C.av_image_get_buffer_size(
		C.AV_PIX_FMT_RGBA, C.int(d.videoSink.Format().Size.Width), C.int(d.videoSink.Format().Size.Height), 1)

	if d.bufSize < 0 {
		return fmt.Errorf(
			"%d: couldn't get the buffer size", d.bufSize)
	}

	buf := (*C.uint8_t)(unsafe.Pointer(
		C.av_malloc(bufferSize(d.bufSize))))

	if buf == nil {
		return fmt.Errorf(
			"couldn't allocate an AV buffer")
	}

	status := C.av_image_fill_arrays(&d.rgbaFrame.data[0],
		&d.rgbaFrame.linesize[0], buf, C.AV_PIX_FMT_RGBA,
		C.int(d.videoSink.Format().Size.Width), C.int(d.videoSink.Format().Size.Height), 1)

	if status < 0 {
		return fmt.Errorf(
			"%d: couldn't fill the image arrays", status)
	}

	d.swsCtx = C.sws_getContext(C.int(d.videoSource.Format().Size.Width),
		C.int(d.videoSource.Format().Size.Height), C.AV_PIX_FMT_YUV420P,
		C.int(d.videoSink.Format().Size.Width), C.int(d.videoSink.Format().Size.Height),
		C.AV_PIX_FMT_RGBA, C.int(C.SWS_BICUBIC), nil, nil, nil)

	if d.swsCtx == nil {
		return fmt.Errorf(
			"couldn't create an SWS context")
	}
	d.wg.Add(1)
	go func() {
		runtime.LockOSThread()
		defer runtime.UnlockOSThread()
		C.run_decoder(unsafe.Pointer(d))
		C.av_free(unsafe.Pointer(d.rgbaFrame))
		C.sws_freeContext(d.swsCtx)
		d.wg.Done()
	}()
	return nil
}

//export goGetDecodingFrame
func goGetDecodingFrame(decoder unsafe.Pointer) *C.AVFrame {
	d := (*Decoder)(decoder)
	return (*C.AVFrame)(unsafe.Pointer(d.FrameFactory.OfferFrame()))
}

//export goPushFrame
func goPushFrame(decoder unsafe.Pointer, frame *C.AVFrame) {
	d := (*Decoder)(decoder)
	C.sws_scale(d.swsCtx, &frame.data[0],
		&frame.linesize[0], 0,
		C.int(d.videoSource.Format().Size.Height),
		&d.rgbaFrame.data[0],
		&d.rgbaFrame.linesize[0])
	d.FrameFactory.PutFrame(AVFrame(unsafe.Pointer(frame)))
	data := C.GoBytes(unsafe.
		Pointer(d.rgbaFrame.data[0]),
		d.bufSize)
	_ = d.videoSink.Output(data)
}

//export goNotifyStopped
func goNotifyStopped(decoder unsafe.Pointer) {
	d := (*Decoder)(decoder)
	if d == nil {
		log.Warn("decoder is nil")
		return
	}
	if event.Bus().HasCallback(event.DecoderStop) {
		event.Bus().Publish(event.DecoderStop, d, d.videoSource, d.videoSink)
	}
}

//export goReadPacket
func goReadPacket(decoder unsafe.Pointer, buf unsafe.Pointer, bufSize C.int) C.int {
	d := (*Decoder)(decoder)
	if d == nil {
		log.Warn("decoder is nil")
		return 1
	}
	var buffer []byte
	pb := (*reflect.SliceHeader)(unsafe.Pointer(&buffer))
	pb.Data = uintptr(buf)
	pb.Cap = int(bufSize)
	pb.Len = int(bufSize)
	if n, err := d.videoSource.Input(buffer); err == io.EOF {
		return -1
	} else if err != nil {
		return -1
	} else {
		return C.int(n)
	}
}

func bufferSize(maxBufferSize C.int) C.ulonglong {
	var byteSize C.ulonglong = 8
	return C.ulonglong(maxBufferSize) * byteSize
}
